package com.atguigu.tingshu.common.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.anno.GuiGuLogin;
import com.atguigu.tingshu.common.anno.GuiguCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumInfo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
@Slf4j
public class GuiguCacheAspect {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedissonClient redissonClient;
    @Around(value = "@annotation(guiguCache)")
    public Object around(ProceedingJoinPoint joinPoint, GuiguCache guiguCache){
        String redisKey = guiguCache.cacheKey() + joinPoint.getArgs()[0].toString();
        //获取目标方法的返回值类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method signatureMethod = signature.getMethod();
        Type returnType = signatureMethod.getGenericReturnType();
        //检查redis中是否有指定缓存内容，有则直接返回
        String json = stringRedisTemplate.opsForValue().get(redisKey);
        if(!StringUtils.isEmpty(json)){
            log.info("从redis中查询到了AlbumInfo数据");
            return JSON.parseObject(json, returnType);
        }

        if (guiguCache.enableLock() && !StringUtils.isEmpty(guiguCache.lockName())) {
            //获取分布锁
            RLock rLock = redissonClient.getLock(guiguCache.lockName() + joinPoint.getArgs()[0].toString());
            if(rLock.tryLock()){ //体现的是限流思想，降低数据库的访问压力
                try {
                    log.info(Thread.currentThread().getName()+"获得了锁对象，并且从数据库中开始查询数据。。。");
                    //从数据库中查询
                    Object object = joinPoint.proceed();
                    stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(object),7, TimeUnit.DAYS);
                    log.info(Thread.currentThread().getName()+"从数据库中查询到数据并将数据放在redis中。。。");
                    return object;
                }catch (Throwable t){
                    throw new RuntimeException(t);
                }finally { //为避免业务代码执行过程中程序出错造成本线程持有的所资源无法释放进而产生死锁问题。需将释放所操作放在finally中保证无论代码执行过程中是否出现异常最终都会释放所资源
                    log.info(Thread.currentThread().getName()+"业务操作执行完毕并且释放了锁资源。。。");
                    rLock.unlock();
                }
            }else {
                log.info(Thread.currentThread().getName()+"未获得锁对象，并自旋继续执行本方法");
                return around(joinPoint,guiguCache);
            }
        }else {
           try {
               Object object = joinPoint.proceed();
               stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(object),7, TimeUnit.DAYS);
               log.info(Thread.currentThread().getName()+"从数据库中查询到数据并将数据放在redis中。。。");
               return object;
           }catch (Throwable t){
               throw new RuntimeException(t);
           }
        }
    }
}
